#!/bin/bash
#
# Copyright (C) 2006-2009 Princeton University
# All rights reserved.
# Author: Christian Bienia
#
# parsecmgmt - A tool to manage the PARSEC benchmark suite
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of Princeton University nor the
#       names of its contributors may be used to endorse or promote products
#       derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY CHRISTIAN BIENIA ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL CHRISTIAN BIENIA BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

# CHANGELOG
#
# From PARSEC 2.0 to PARSEC 2.1:
#  - Made PARSECPLAT string detection more robust
#  - Place log files in separate log/${PARSECPLAT} subdirectories
#  - Always append build config name to PARSECPLAT
#
# From PARSEC 1.0 to PARSEC 2.0:
#  - parsecmgmt now adds '$CC_HOME/lib' and '$CC_HOME/lib64' to LD_LIBRARY_PATH
#  - file 'compile-info' renamed to 'build-info'
#  - option `-x' added to append extention to platform descriptor string
#  - option `-k' to keep run directory
#  - eliminated need for "inputs" list from file parsec.conf
#  - auto-detect available aliases and packages
#  - conditional package dependencies (also depend on build configuration now)
#  - Renamed action `uninstallall' to `fulluninstall'
#  - Renamed action `cleanall' to `fullclean'



#################################################################
#                                                               #
#                         CONFIGURATION                         #
#                                                               #
#################################################################

# Define directory of PARSEC benchmark suite
# If $PARSECDIR is not set, the script tries to autodetect the path
#export PARSECDIR=""

# Define platform name to use
# If $PARSECPLAT is not set, the script will automaticaly determine a name
#export PARSECPLAT=""

# Should output be logged? Anything other than "TRUE" will turn off logging
create_log="TRUE"



##### There should be no need to touch anything below here #####

# Output prefix to use
oprefix="[PARSEC]"



#################################################################
#                                                               #
#                           FUNCTIONS                           #
#                                                               #
#################################################################

# parsec_init
#
# Initialize the script
#
# Arguments: none
function parsec_init {
  # We need to hard-wire a few commands because we need them for the path detection
  PWD="pwd"
  BASENAME="basename"
  DIRNAME="dirname"

  # Determine script name
  eval me=$(${BASENAME} $0)

  # Try to find path
  uniquefile=".parsec_uniquefile"
  parsecdir=""
  if [ ! -z "${PARSECDIR}" ]; then
    # User defined PARSECDIR, check it
    parsecdir="${PARSECDIR}"
    if [ ! -f "${parsecdir}/${uniquefile}" ]; then
      echo "${oprefix} Error: Variable PARSECDIR points to '${PARSECDIR}', but this does not seem to be the PARSEC directory. Either unset PARSECDIR to make me try to autodetect the path or set it to the correct value."
      exit 1
    fi
  else
    # Try to autodetect path by looking at path used to invoke this script

    # Try to extract absoute or relative path
    if [ "${0:0:1}" == "/" ]; then
      # Absolute path given
      eval parsecdir=$(${DIRNAME} $(${DIRNAME} $0))
      # Check
      if [ -f "${parsecdir}/${uniquefile}" ]; then
        PARSECDIR=${parsecdir}
      fi
    else
      # No absolute path, maybe relative path?
      eval parsecdir=$(${PWD})/$(${DIRNAME} $(${DIRNAME} $0))
      # Check
      if [ -f "${parsecdir}/${uniquefile}" ]; then
        PARSECDIR=${parsecdir}
      fi
    fi

    # If PARSECDIR is still undefined, we try to guess the path
    if [ -z "${PARSECDIR}" ]; then
      # Check current directory
      if [ -f "./${uniquefile}" ]; then
        parsecdir="$(${PWD})"
        PARSECDIR=${parsecdir}
      fi
    fi
    if [ -z "${PARSECDIR}" ]; then
      # Check next-higher directory
      if [ -f "../${uniquefile}" ]; then
        parsecdir="$(${PWD})/.."
        PARSECDIR=${parsecdir}
      fi
    fi
  fi

  # Make sure PARSECDIR is defined and exported
  if [ -z "${PARSECDIR}" ]; then
    echo "${oprefix} Error: Unable to autodetect path to the PARSEC benchmark suite. Either define an environment variable PARSECDIR, or edit ${me} and set PARSECDIR to the correct value at the beginning of the file."
    exit 1
  fi
  export PARSECDIR

  # Eliminate trailing `/.' from PARSECDIR
  PARSECDIR=${PARSECDIR/%\/./}

  # Determine OS name to use for automatically determined PARSECPLAT
  case "${OSTYPE}" in
  *linux*)   ostype="linux";;
  *solaris*) ostype="solaris";;
  *bsd*)     ostype="bsd";;
  *aix*)     ostype="aix";;
  *hpux*)    ostype="hpux";;
  *irix*)    ostype="irix";;
  *amigaos*) ostype="amigaos";;
  *beos*)    ostype="beos";;
  *bsdi*)    ostype="bsdi";;
  *cygwin*)  ostype="windows";;
  *darwin*)  ostype="darwin";;
  *interix*) ostype="interix";;
  *os2*)     ostype="os2";;
  *osf*)     ostype="osf";;
  *sunos*)   ostype="sunos";;
  *sysv*)    ostype="sysv";;
  *sco*)     ostype="sco";;
  *)         ostype="${OSTYPE}";;
  esac

  # Determine HOST name to use for automatically determined PARSECPLAT
  case "${HOSTTYPE}" in
  *i386*)    hosttype="i386";;
  *x86_64*)  hosttype="amd64";;
  *amd64*)   hosttype="amd64";;
  *i486*)    hosttype="amd64";;
  *sparc*)   hosttype="sparc";;
  *sun*)     hosttype="sparc";;
  *ia64*)    hosttype="ia64";;
  *itanium*) hosttype="ia64";;
  *powerpc*) hosttype="powerpc";;
  *ppc*)     hosttype="powerpc";;
  *alpha*)   hosttype="alpha";;
  *mips*)    hosttype="mips";;
  *arm*)     hosttype="arm";;
  *)         hosttype="${HOSTTYPE}";;
  esac

  # Determine first part of value to use for PARSECPLAT environment variable if not defined by user
  # Note: We will append the compiler configuration to that to get the final value for PARSECPLAT
  hostostype="${hosttype}-${ostype}"

  # Define some global directories
  benchdir=${parsecdir}/pkgs
  logdir=${parsecdir}/log

  # Source global configuration file with alias definitions, package dependencies etc.
  parsecconfig="${PARSECDIR}/config/parsec.conf"
  if [ -f "${parsecconfig}" ]; then
    source ${parsecconfig}
  else
    echo "${oprefix} Error: Cannot load global configuration file '${parsecconfig}'."
    exit 1
  fi

  # Try to load OS-specific configuration to get binaries and correct arguments
  sysconfig="${PARSECDIR}/config/${ostype}.sysconf"
  if [ -f "${sysconfig}" ]; then
    source ${sysconfig}
  else
    echo "${oprefix} Error: Cannot load system configuration file '${sysconfig}' for OS type '${ostype}'. Please create a new system configuration file."
    exit 1
  fi

  # Setup environment so PARSEC tools are usable by other programs
  if [ -z "${PATH}" ]; then
    export PATH="${PARSECDIR}/bin"
  else
    export PATH="${PARSECDIR}/bin:${PATH}"
  fi
}

# process_args
#
# Process args and setup argument-dependent environment
#
# Arguments: all arguments given to the script (i.e. "$@")
function process_args {
  # Default configuration to use
  default_pkgs="bench"
  default_build="gcc"
  default_rundir="${benchdir}"
  default_inputsize="test"
  default_nthreads="1"
  default_submit="time"

  # Usage
  usage="\
Usage: $me -a ACTION [OPTION]...

Manage the installation of the PARSEC benchmark suite.

Options:
    -a ACTION        Specifies the action to perform. See below for a
                     list of valid actions.
    -p PACKAGE       A list of packages or aliases on which the action is to be
                     performed.
    -c CONFIG        Which build configuration to use. Default: '$default_build'
    -d RUNDIR        Use directory RUNDIR as root in which to run the
                     benchmarks.
    -i INPUT         The input to use to run the benchmarks. Default: '$default_inputsize'
    -n THREADS       The minimum number of threads to use. Default: '$default_nthreads'
    -s SUBMIT        Command to use to submit the benchmark for execution.
                     Default: '$default_submit'
    -x EXTENSION     Extension to append to platform descriptor string.
                     Default: none
    -k               Keep & use run directory as found, do not unpack inputs for
                     benchmark execution. Assume everything is already set up.
    -h               Displays this help message.

Actions:
    'build'          Builds and installs the specified packages.
    'run'            Runs the specified packages.
    'clean'          Removes all files generated during the build and run phase
                     of the listed packages for the current platform, but
                     leaves the installed files untouched.
    'uninstall'      Removes the installed files of the listed packages for the
                     current platform.
    'fullclean'      Like 'clean', but for all architectures.
    'fulluninstall'  Like 'uninstall', but for all architectures.
    'status'         Shows a summary of the current status of the PARSEC
                     installation.
    'info'           List available packages and configurations.

Examples:
    - Build the complete suite:
        $me -a build -p all
    - Build only applications, use 'icc' as build configuration:
        $me -a build -c icc -p apps
    - Do a full cleanup after a build or benchmark run:
        $me -a fullclean -p all
    - Remove all generated files of the current architecture:
        $me -a uninstall -p all
    - Run benchmark 'ferret' with input 'simsmall' and 4 threads:
        $me -a run -i simsmall -n 4
    - Get a quick summary of all available packages and features:
        $me -a info
    - Show which kernel binaries have been installed:
        $me -a status -p kernels"


  # Define valid actions
  valid_actions="build run clean uninstall fullclean fulluninstall status info"

  # Parse arguments
  parsemode="none"
  need_arg_for=""
  action=""
  pkgs=""
  build=""
  rundir=""
  inputsize=""
  nthreads=""
  submit=""
  extension=""
  keep_rundir=""
  while [ ! -z "$1" ]; do
    arg="$1"
    case "${arg}" in
      "-a" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-a'"
          echo "${usage}"
          exit 1
        fi
        if [ ! -z "${action}" ]; then
          echo "${oprefix} Error: Two actions specified"
          exit 1
        fi
        need_arg_for="-a"
        parsemode="ACTION";;
      "-p" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-p'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-p"
        parsemode="PKGS";;
      "-c" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-c'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-c"
        parsemode="CONFIG";;
      "-d" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-d'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-d"
        parsemode="RUNDIR";;
      "-i" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-i'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-i"
        parsemode="INPUT";;
      "-n" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-n'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-n"
        parsemode="NTHREADS";;
      "-s" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-s'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-s"
        parsemode="SUBMIT";;
      "-x" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-x'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for="-s"
        parsemode="EXTENSION";;
      "-k" )
        if [ ! -z "${need_arg_for}" ]; then
          echo "${oprefix} Error: ${parsemode} expected between '${need_arg_for}' and '-k'"
          echo "${usage}"
          exit 1
        fi
        keep_rundir="yes";;
      "-h" )
        echo "${usage}"
        exit 0;;
      *    )
        if [ ${arg:0:1} == "-" ]; then
          echo "${oprefix} Error: Unknown argument '${arg}'"
          echo "${usage}"
          exit 1
        fi
        need_arg_for=""
        case "${parsemode}" in
          "ACTION"   )
            parsemode="none"
            # Check whether specified action is valid
            is_valid=""
            for valid_action in ${valid_actions}; do
              if [ "${arg}" == "${valid_action}" ]; then
                is_valid="TRUE"
                break
              fi
            done
            if [ -z "$is_valid" ]; then
              echo "${oprefix} Error: Unknown action '${arg}'."
              echo "${usage}"
              exit 1
            fi
            action="${arg}";;
          "PKGS"   )
            # Check whether specified package is valid
            is_valid=""
            # Is it a recognized alias?
            eval temp="\"\${alias_${arg}}\""
            if [ ! -z "${temp}" ]; then
              is_valid="TRUE"
            fi
            # Is it a recognized package?
            eval temp="\"\${group_${arg}}\""
            if [ ! -z "${temp}" ]; then
              is_valid="TRUE"
            fi
            # Interpret result
            if [ -z "$is_valid" ]; then
              echo "${oprefix} Error: Invalid package '${arg}'."
              exit 1
            fi
            pkgs="${pkgs} ${arg}";;
          "CONFIG"   )
            parsemode="none"
            # Check whether specified config file exists
            buildfile="${PARSECDIR}/config/${arg}.bldconf"
            if [ ! -f "${buildfile}" ]; then
              echo "${oprefix} Error: Cannot find build configuration file '${buildfile}'."
              exit 1
            fi
            build="${arg}";;
          "RUNDIR"   )
            parsemode="none"
            # Check whether specified run directory exists
            if [ ! -d "${arg}" ]; then
              echo "${oprefix} Error: Cannot find directory '${arg}'."
              exit 1
            fi
            rundir="${arg}";;
          "INPUT"    )
            parsemode="none"
            # Check whether specified input set exists
            runconfigfile="${PARSECDIR}/config/${arg}.runconf"
            if [ ! -f "${runconfigfile}" ]; then
              echo "${oprefix} Error: Unknown input set '${arg}'."
              exit 1
            fi
            inputsize="${arg}";;
          "NTHREADS" )
            parsemode="none"
            declare -i nthreads
            nthreads="${arg}"
            # Note: This check also filters out strings (nthreads==0 in that case)
            if [ "${nthreads}" -lt "1" ]; then
              echo "${oprefix} Error: Illegal number of threads: '${arg}'."
              echo "${usage}"
              exit 1
            fi;;
          "SUBMIT" )
            parsemode="none"
            submit="${arg}";;
          "EXTENSION" )
            parsemode="none"
            extension="${arg}";;
          *        )
            echo "${oprefix} Error: Unknown argument '${arg}'"
            echo "${usage}"
            exit 1;;
        esac;;
    esac

    shift
  done
  if [ ! -z "${need_arg_for}" ]; then
    echo "${oprefix} Error: ${parsemode} expected after '${need_arg_for}'"
    echo "${usage}"
    exit 1
  fi

  # Make sure we have an action and at least one package
  if [ -z "${action}" ]; then
    echo "${oprefix} Error: No action specified."
    echo "${usage}"
    exit 1
  fi
  if [ -z "${pkgs}" ]; then
    # Use default value
    pkgs="${default_pkgs}"
  fi

  # Set NTHREADS
  if [ -z "${nthreads}" ]; then
    # Use default value
    NTHREADS="${default_nthreads}"
  else
    NTHREADS="${nthreads}"
  fi

  # Set rundir
  if [ -z "${rundir}" ]; then
    # Use default value
    rundir="${default_rundir}"
  fi

  # Set input size
  if [ -z "${inputsize}" ]; then
    # Use default value
    inputsize="${default_inputsize}"
  fi

  # Set submit program
  if [ -z "${submit}" ]; then
    # Use default value
    submit="${default_submit}"
  fi

  # Determine build
  if [ -z "${build}" ]; then
    build="${default_build}"
  fi

  # Define PARSECPLAT, if not set by user
  if [ -z "${PARSECPLAT}" ]; then
    PARSECPLAT=${hostostype}.${build}
  else
    PARSECPLAT=${PARSECPLAT}.${build}
  fi
  # Append extension if specified
  if [ ! -z "${extension}" ]; then
    PARSECPLAT=${PARSECPLAT}.${extension}
  fi

  export PARSECPLAT
  # Load build configuration
  if [ "${action}" == "build" -o "${action}" == "run" ]; then
    buildfile="${PARSECDIR}/config/${build}.bldconf"
    if [ -f "${buildfile}" ]; then
      source ${buildfile}
    else
      echo "${oprefix} Error: Cannot find build configuration file '${buildfile}'."
      exit 1
    fi
  fi

  # Expand aliases to get full list of packages to build
  expanded_aliases=""
  for token in ${pkgs}; do
    # Is it a recognized alias?
    eval temp="\"\${alias_${token}}\""
    if [ ! -z "${temp}" ]; then
      # Yes it is, expand alias
      expanded_aliases="${expanded_aliases} ${temp}"
    else
      # Otherwise keep it in the list
      expanded_aliases="${expanded_aliases} ${token}"
    fi
  done

  # Rebuild list with packages, remove duplicate packages
  pkgs=""
  for pkg in $expanded_aliases; do
    is_included=""
    for dummy in $pkgs; do
      if [ "$pkg" == "$dummy" ]; then
        is_included="TRUE"
        break
      fi
    done
    if [ -z "$is_included" ]; then
      pkgs="${pkgs} ${pkg}"
    fi
  done

  # Initialize logging functionality
  logfile=""
  if [ "${action}" == "build" ]; then
    eval logfile="build_\$(${LOGDATE}).log"
  elif [ "${action}" == "run" ]; then
    eval logfile="run_\$(${LOGDATE}).log"
  fi
  if [ "$create_log" == "TRUE" ] && [ ! -z "$logfile" ]; then
    ${MKDIR} ${logdir}/${PARSECPLAT}
    log=${logdir}/${PARSECPLAT}/${logfile}
  else
    log="/dev/null"
  fi
}

# build_info
#
# Create a text file containing information about the build.
# Assumes that all relevant configuration files have already been sourced.
#
# Arguments: $1 - Package name
#            $2 - The name of the text file to create
function build_info {
  if [ -z "$1" ]; then
    echo "${oprefix} Error: build_info called with no arguments." 2>&1 | ${TEE} ${log}
    exit 1
  fi
  local package_name="$1"
  if [ -z "$2" ]; then
    echo "${oprefix} Error: build_info called with insufficient number of arguments." 2>&1 | ${TEE} ${log}
    exit 1
  fi
  local cinfo_file="$2"

  echo "PARSEC Compile Information" > ${cinfo_file}
  echo "==========================" >> ${cinfo_file}
  echo "Package '${package_name}'" >> ${cinfo_file}
  eval echo "Built on \$(${DATE})" >> ${cinfo_file}
  echo >> ${cinfo_file}

  # Package-specific build information
  local env_package="${build_env}"
  local build_conf_package="${build_conf} ${build_conf_global}"

  echo "Configure arguments: ${build_conf_package}" >> ${cinfo_file}
  echo >> ${cinfo_file}
  echo "Environment modifications: ${env_package}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "CC: ${CC}" >> ${cinfo_file}
  echo "Version: ${CC_ver}" >> ${cinfo_file}
  echo "CFLAGS: ${CFLAGS}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "CXX: ${CXX}" >> ${cinfo_file}
  echo "Version: ${CXX_ver}" >> ${cinfo_file}
  echo "CXXFLAGS: ${CXXFLAGS}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "CCAS: ${CCAS}" >> ${cinfo_file}
  echo "Version: ${CCAS_ver}" >> ${cinfo_file}
  echo "CCASFLAGS: ${CCASFLAGS}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "LD: ${LD}" >> ${cinfo_file}
  echo "Version: ${LD_ver}" >> ${cinfo_file}
  echo "LDFLAGS: ${LDFLAGS}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "JAVAC: ${JAVAC}" >> ${cinfo_file}
  echo "Version: ${JAVAC_ver}" >> ${cinfo_file}
  echo "JFLAGS: ${JFLAGS}" >> ${cinfo_file}
  echo >> ${cinfo_file}

  echo "[EOF]" >> ${cinfo_file}
}

# build_package
#
# Build a software package and recursively all packages it depends on
#
# Arguments: $1 - The package name
function build_package {
  if [ -z "$1" ]; then
    echo "${oprefix} Error: build_package called with no arguments." 2>&1 | ${TEE} ${log}
    exit 1
  fi
  local pkg=$1

  echo "${oprefix} [---------- Analyzing package ${pkg} ----------]" 2>&1 | ${TEE} ${log}

  # Define package-specific directories
  eval local pkggroup=\${group_${pkg}}
  if [ -z "$pkggroup" ]; then
    echo "${oprefix} Error: Group of package ${pkg} is unknown." 2>&1 | ${TEE} ${log}
    exit 1
  fi
  local pkgdir=${benchdir}/${pkggroup}/${pkg}
  local pkgsrcdir=${pkgdir}/src
  local pkgobjdir=${pkgdir}/obj/${PARSECPLAT}
  local pkginstdir=${pkgdir}/inst/${PARSECPLAT}
  local pkgparsecdir=${pkgdir}/parsec

  # Check whether package has already been built
  if [ -f "${pkginstdir}/build-info" ]; then
    echo "${oprefix} Package ${pkg} already exists, proceeding." 2>&1 | ${TEE} ${log}
    return 0
  fi

  # Source local build configuration to get dependencies
  if [ ! -f "${pkgparsecdir}/${build}.bldconf" ]; then
    echo "${oprefix} Error: Cannot find local build configuration '${build}.bldconf' for package ${pkg}." 2>&1 | ${TEE} ${log}
    exit 1
  else
    source ${pkgparsecdir}/${build}.bldconf
  fi

  # Build packages on which pkg depends
  if [ -z "${build_deps}" ]; then
    echo "${oprefix} ${pkg} does not depend on any other packages." 2>&1 | ${TEE} ${log}
  else
    echo "${oprefix} ${pkg} depends on: ${build_deps}" 2>&1 | ${TEE} ${log}
  fi
  local dep=""
  for dep in ${build_deps}; do
    build_package "${dep}"
  done

  # Now build pkg itself
  echo "${oprefix} [---------- Building package ${pkg} ----------]" 2>&1 | ${TEE} ${log}

  # Source local package build configuration again (might have gotten overwritten by recursion)
  if [ ! -f "${pkgparsecdir}/${build}.bldconf" ]; then
    echo "${oprefix} Error: Cannot find local build configuration '${build}.bldconf' for package ${pkg}." 2>&1 | ${TEE} ${log}
    exit 1
  else
    source ${pkgparsecdir}/${build}.bldconf
  fi

  # Check for obvious errors
  if [ -z "${build_inplace}" ]; then
    echo "${oprefix} Variable 'build_inplace' for package '${benchmark}' has not been set in file '${build}.bldconf'." | ${TEE} ${log}
    exit 1
  fi

  # Global arguments to configure script
  build_conf_global="--prefix=${pkginstdir}"

  # We need either configure or a Makefile
  if [ ! -x ${pkgsrcdir}/configure -a ! -f ${pkgsrcdir}/[Mm]akefile ]; then
    echo "${oprefix} Error: Need 'configure' script or a '[Mm]akefile' to build package ${pkg}." 2>&1 | ${TEE} ${log}
    exit 1
  fi

  # Remove an already existing build directory
  if [ -e "${pkgobjdir}" ]; then
     echo "${oprefix} Removing old build directory." 2>&1 | ${TEE} ${log}
     ${RM} ${pkgobjdir} 
  fi

  # Set up build directory
  if [ "${build_inplace}" == "TRUE" ]; then
    local pkgbuildsrcdir=${pkgobjdir}
    echo "${oprefix} Copying source code of package ${pkg}." 2>&1 | ${TEE} ${log}
    ${MKDIR} ${pkgobjdir} > /dev/null
    ${CP} ${pkgsrcdir}/* ${pkgobjdir}
  else
    local pkgbuildsrcdir=${pkgsrcdir}
    ${MKDIR} ${pkgobjdir} > /dev/null
  fi
  pushd ${pkgobjdir} > /dev/null

  # Run configure
  if [ -x ${pkgbuildsrcdir}/configure ]; then
    local build_conf_pkg="${build_conf} ${build_conf_global}"
    # Current pwd is $pkgobjdir
    if [ "${pkgobjdir}" == "${pkgbuildsrcdir}" ]; then
      local configcmd="env ${build_env} ./configure ${build_conf_pkg}"
    else
      local configcmd="env ${build_env} ${pkgbuildsrcdir}/configure ${build_conf_pkg}"
    fi
    eval echo "${oprefix} Running \'${configcmd}\':" 2>&1 \| ${TEE} ${log}
    eval \( ${configcmd} 2>&1 \) 2>&1 \| ${TEE} ${log} \; retval=\${PIPESTATUS[0]}
    if [ "$retval" -ne "0" ]; then
      eval echo "${oprefix} Error: \'${configcmd}\' failed." 2>&1 \| ${TEE} ${log}
      popd > /dev/null
      exit 1
    fi
  fi
  
  # Make sure a Makefile exists in the build directory
  if [ ! -f ${pkgobjdir}/[Mm]akefile ]; then
    echo "${oprefix} Error: Cannot find Makefile in ${pkgobjdir}" 2>&1 | ${TEE} ${log}
    popd > /dev/null
    exit 1
  fi

  # Run make
  local makecmd="env ${build_env} ${MAKE}"
  eval echo "${oprefix} Running \'$makecmd\':" 2>&1 \| ${TEE} ${log}
  eval \( ${makecmd} 2>&1 \) 2>&1 \| ${TEE} ${log} \; retval=\${PIPESTATUS[0]}
  if [ "$retval" -ne "0" ]; then
    eval echo "${oprefix} Error: \'${makecmd}\' failed." 2>&1 \| ${TEE} ${log}
    popd > /dev/null
    exit 1
  fi

  # Run make install
  ${MKDIR} ${pkginstdir} > /dev/null
  local makecmd="env ${build_env} ${MAKE} install"
  eval echo "${oprefix} Running \'$makecmd\':" 2>&1 \| ${TEE} ${log}
  eval \( ${makecmd} 2>&1 \) 2>&1 \| ${TEE} ${log} \; retval=\${PIPESTATUS[0]}
  if [ "$retval" -ne "0" ]; then
    eval echo "${oprefix} Error: \'${makecmd}\' failed." 2>&1 \| ${TEE} ${log}
    popd > /dev/null
    exit 1
  fi

  popd > /dev/null
  pushd ${pkginstdir} > /dev/null

  # Create build info file
  build_info ${pkg} ${pkginstdir}/build-info

  # Finish up
  popd > /dev/null
}

# run_benchmark
#
# Run the specified benchmarks
#
# Arguments: $1 - The benchmark name
#            $2 - Which input to use
#            $3 - The run directory root
function run_benchmark {
  if [ -z "$1" ]; then
    echo "${oprefix} Error: run_benchmark called with no arguments." | ${TEE} ${log}
    exit 1
  fi
  local benchmark=$1
  if [ -z "$2" ]; then
    echo "${oprefix} Error: run_benchmark called with insufficient number of arguments." | ${TEE} ${log}
    exit 1
  fi
  local inputsize=$2
  if [ -z "$3" ]; then
    echo "${oprefix} Error: run_benchmark called with insufficient number of arguments." | ${TEE} ${log}
    exit 1
  fi
  local rundir=$3

  echo "${oprefix} [========== Running benchmark ${benchmark} ==========]" | ${TEE} ${log}

  # Define benchmark-specific directories
  eval local benchmarkgroup=\${group_${benchmark}}
  if [ -z "${benchmarkgroup}" ]; then
    echo "${oprefix} Error: Group of benchmark '${benchmark}' is unknown." | ${TEE} ${log}
    exit 1
  fi
  local bmrundir="${rundir}/${benchmarkgroup}/${benchmark}/run"
  local bmdir="${PARSECDIR}/pkgs/${benchmarkgroup}/${benchmark}"
  local bminputdir="${bmdir}/inputs"
  local bmoutputdir="${bmdir}/outputs"
  local bminstdir="${bmdir}/inst/${PARSECPLAT}"
  local bmparsecdir="${bmdir}/parsec"

  # Check whether selected input size is available
  if [ ! -f "${PARSECDIR}/config/${inputsize}.runconf" ]; then
    echo "${oprefix} Error: Selected input size not available."  2>&1 | ${TEE} ${log}
    exit 1
  fi

  # Source package run configuration
  if [ ! -f "${bmparsecdir}/${inputsize}.runconf" ]; then
    echo "${oprefix} Error: Cannot find run configuration '${inputsize}.runconf' for package ${pkg}." 2>&1 | ${TEE} ${log}
    exit 1
  else
    source ${bmparsecdir}/${inputsize}.runconf
  fi

  # Determine binary
  local bmexec_suffix=${run_exec}
  if [ -z "${bmexec_suffix}" ]; then
    echo "${oprefix} Variable 'bench_exec_${benchmark}' for package '${benchmark}' has not been set." | ${TEE} ${log}
    exit 1
  fi
  local bmexec="${bminstdir}/${bmexec_suffix}"
  if [ ! -x "${bmexec}" ]; then
    echo "${oprefix} Error: Binary '${bmexec}' of package '${benchmark}' cannot be found." | ${TEE} ${log}
    exit 1
  fi

  # Determine output archive
  local bmoutput="${bmoutputdir}/output_${inputsize}.tar"
  #if [ ! -f "${bmoutput}" ]; then
  #  echo "${oprefix} Error: Output archive '${bmoutput}' of package '${benchmark}' cannot be found." | ${TEE} ${log}
  #  exit 1
  #fi

  if [ "${keep_rundir}" == "yes" ]; then
    # Keep run directory, just check that it exists
    if [ ! -d "${bmrundir}" ]; then
      echo "${oprefix} Error: Cannot keep run directory because it does not exist." | ${TEE} ${log} 
      exit 1
    fi
    echo "${oprefix} Keeping old run directory." | ${TEE} ${log}
    pushd "${bmrundir}" > /dev/null
  else 
    # Set up run directory
    if [ -d "${bmrundir}" ]; then
      echo "${oprefix} Deleting old run directory." | ${TEE} ${log}
      ${RM} ${bmrundir} | ${TEE} ${log}
    fi

    echo "${oprefix} Setting up run directory." | ${TEE} ${log}
    ${MKDIR} ${bmrundir} | ${TEE} ${log}
    pushd "${bmrundir}" > /dev/null

    # Determine input archive
    local bminput="${bminputdir}/input_${inputsize}.tar"
    if [ ! -f "${bminput}" ]; then
      echo "${oprefix} No archive for input '${inputsize}' available, skipping input setup." | ${TEE} ${log}
    else
      echo "${oprefix} Unpacking benchmark input '${inputsize}'." | ${TEE} ${log}
      ${UNTAR} ${bminput} | ${TEE} ${log}
    fi
  fi

  # Execute
  local bmexec_args=${run_args}
  eval cmd="\"${submit} ${bmexec} ${bmexec_args}\""
  echo "${oprefix} Running '${cmd}':" | ${TEE} ${log}
  echo "${oprefix} [---------- Beginning of output ----------]" | ${TEE} ${log}

  set -o pipefail
  eval ${cmd}
  # 2>&1) 2>&1 | ${TEE} ${bmrundir}/benchmark.out | ${TEE} ${log}
  CMD_RC=$?

  echo "${oprefix} [----------    End of output    ----------]" | ${TEE} ${log}


  # Unpack reference output and compare
  #echo "${oprefix} Unpacking benchmark reference output for '${inputsize}'." | ${TEE} ${log}
  #${MKDIR} ${bmrundir}/output.ref | ${TEE} ${log}
  #cd ${bmrundir}/output.ref
  #${UNTAR} ${bmoutput} | ${TEE} ${log}

  #echo "${oprefix} Checking benchmark output:" | ${TEE} ${log}
  #bmcorrect="TRUE"
  #for outfile in *; do
  #  echo -n "${oprefix}   ${outfile} - " | ${TEE} ${log}
  #  if [ ! -f "${bmrundir}/${outfile}" ]; then
  #    echo "MISSING" | ${TEE} ${log}
  #    bmcorrect="FALSE"
  #  else
  #    ${CMP} ${bmrundir}/output.ref/${outfile} ${bmrundir}/${outfile} > /dev/null
  #    if [ "$?" == "0" ]; then
  #      echo "PASSED" | ${TEE} ${log}
  #    else
  #      echo "FAILED" | ${TEE} ${log}
  #      bmcorrect="FALSE"
  #    fi
  #  fi
  #done
  #if [ "${bmcorrect}" != "TRUE" ]; then
  #  echo "${oprefix} Error: Benchmark not correctly executed." | ${TEE} ${log}
  #  exit 1
  #else
  #  echo "${oprefix} Benchmark correctly executed." | ${TEE} ${log}
  #fi
  popd > /dev/null
  return "${CMD_RC}"
}



#################################################################
#                                                               #
#                             MAIN                              #
#                                                               #
#################################################################

# Check version
if [ ${BASH_VERSINFO[0]} -lt 3 ]; then
  # We need certain Bash 3 features. e.g. PIPESTATUS (which was available but broken in earlier versions)
  echo "${oprefix} Warning: At least bash version 3 is recommended. Earlier versions might not function properly. Current version is $BASH_VERSION."
fi

# Execute functions to setup environment
parsec_init
process_args "$@"

# Now we take decisive action
case ${action} in
  "build"         )
    eval echo "${oprefix} \[========== PARSEC $(${CAT} ${PARSECDIR}/version) BUILD LOGFILE ==========\]" > ${log}
    echo >> ${log}
    eval echo "${oprefix} Build '${PARSECPLAT}' started on \$(${DATE})." >> ${log}
    echo "${oprefix} Packages to build: ${pkgs}" 2>&1 | ${TEE} ${log}

    # Prepare LD_LIBRARY_PATH (some build systems call their own binaries)
    LD_LIBRARY_PATH_orig="${LD_LIBRARY_PATH}"
    if [ -z "${LD_LIBRARY_PATH}" ]; then
      export LD_LIBRARY_PATH="${CC_HOME}/lib:${CC_HOME}/lib64"
    else
      LD_LIBRARY_PATH="${CC_HOME}/lib:${CC_HOME}/lib64:${LD_LIBRARY_PATH}"
    fi

    # Build all packages
    for pkg in ${pkgs}; do
      echo | ${TEE} ${log}
      echo "${oprefix} [========== Building package ${pkg} ==========]" | ${TEE} ${log}
      build_package "${pkg}"
    done

    # Restore LD_LIBRARY_PATH
    LD_LIBRARY_PATH="${LD_LIBRARY_PATH_orig}"

    echo "${oprefix} Done." | ${TEE} ${log};;
  "run"           )
    eval echo "${oprefix} \[========== PARSEC $(${CAT} ${PARSECDIR}/version) RUN LOGFILE ==========\]" > ${log}
    echo >> ${log}
    eval echo "${oprefix} Run with '${PARSECPLAT}' binaries and input ${inputsize} started on \$(${DATE})." >> ${log}
    echo "${oprefix} Using ${rundir} as run directory root." >> ${log}
    echo "${oprefix} Benchmarks to run: ${pkgs}" 2>&1 | ${TEE} ${log}

    # Prepare LD_LIBRARY_PATH
    LD_LIBRARY_PATH_orig="${LD_LIBRARY_PATH}"
    if [ -z "${LD_LIBRARY_PATH}" ]; then
      export LD_LIBRARY_PATH="${CC_HOME}/lib:${CC_HOME}/lib64"
    else
      LD_LIBRARY_PATH="${CC_HOME}/lib:${CC_HOME}/lib64:${LD_LIBRARY_PATH}"
    fi

    # Execute all benchmarks
    # Keep track of failures, and report then
    ALL_RC=0
    for benchmark in ${pkgs}; do
      echo | ${TEE} ${log}
      run_benchmark "${benchmark}" "${inputsize}" "${rundir}"
      RC="${?}"
      if [ "${RC}" -ne 0 ]; then
        ALL_RC="${RC}"
      fi
    done

    # Restore LD_LIBRARY_PATH
    LD_LIBRARY_PATH="${LD_LIBRARY_PATH_orig}"

    echo "${oprefix} Done." | ${TEE} ${log}
    exit "${ALL_RC}";;
  "clean"         )
    echo "${oprefix} Removing build and run files for build '${PARSECPLAT}':"
    for package in ${pkgs}; do
      # Setup
      eval packagedir="${benchdir}/\${group_${package}}/${package}"
      packageobjdir="${packagedir}/obj/${PARSECPLAT}"
      packageparsecdir="${packagedir}/parsec"
      eval packagerundir="${rundir}/\${group_${package}}/${package}/run"
      cleanenv="sysconfig=\"${sysconfig}\" packagedir=\"${packagedir}\""
      nothingtodo=""

      echo -n "${oprefix} ${package} - "

      # Remove build directory
      if [ -d "${packageobjdir}" ]; then
        echo -n "[Removing build '${PARSECPLAT}'] "
        ${RM} ${packageobjdir}
        nothingtodo="FALSE"
      fi

      # Remove run directory
      if [ -d "${packagerundir}" ]; then
        echo -n "[Removing run directory]"
        ${RM} ${packagerundir}
        nothingtodo="FALSE"
      fi

      # Finish
      if [ -z "${nothingtodo}" ]; then
        echo -n "[Nothing to do]"
      fi
      echo ""
    done
    exit 0;;
  "uninstall"     )
    echo "${oprefix} Removing installations for build '${PARSECPLAT}':"
    for package in ${pkgs}; do
      # Setup
      eval packagedir="${benchdir}/\${group_${package}}/${package}"
      packageinstdir="${packagedir}/inst/${PARSECPLAT}"
      packageparsecdir="${packagedir}/parsec"
      cleanenv="sysconfig=\"${sysconfig}\" packagedir=\"${packagedir}\""
      nothingtodo=""

      echo -n "${oprefix} ${package} - "

      # Remove inst directory
      if [ -d "${packageinstdir}" ]; then
        echo -n "[Removing installation '${PARSECPLAT}'] "
        ${RM} ${packageinstdir}
        nothingtodo="FALSE"
      fi

      # Finish
      if [ -z "${nothingtodo}" ]; then
        echo -n "[Nothing to do]"
      fi
      echo ""
    done
    exit 0;;
  "fullclean"     )
    echo "${oprefix} Removing all build and run files:"
    for package in ${pkgs}; do
      # Setup
      eval packagedir="${benchdir}/\${group_${package}}/${package}"
      packageobjdir="${packagedir}/obj"
      packageparsecdir="${packagedir}/parsec"
      eval packagerundir="${rundir}/\${group_${package}}/${package}/run"
      cleanenv="sysconfig=\"${sysconfig}\" packagedir=\"${packagedir}\""
      nothingtodo=""

      echo -n "${oprefix} ${package} - "

      # Remove obj directory
      if [ -d "${packageobjdir}" ]; then
        echo -n "[Removing build directory] "
        ${RM} ${packageobjdir}
        nothingtodo="FALSE"
      fi

      # Remove run directory
      if [ -d "${packagerundir}" ]; then
        echo -n "[Removing run directory] "
        ${RM} ${packagerundir}
        nothingtodo="FALSE"
      fi

      # Finish
      if [ -z "${nothingtodo}" ]; then
        echo -n "[Nothing to do]"
      fi
      echo ""
    done
    exit 0;;
  "fulluninstall" )
    echo "${oprefix} Removing all installed files:"
    for package in ${pkgs}; do
      # Setup
      eval packagedir="${benchdir}/\${group_${package}}/${package}"
      packageinstdir="${packagedir}/inst"
      packageparsecdir="${packagedir}/parsec"
      cleanenv="sysconfig=\"${sysconfig}\" packagedir=\"${packagedir}\""
      nothingtodo=""

      echo -n "${oprefix} ${package} - "

      # Remove inst directory
      if [ -d "${packageinstdir}" ]; then
        echo -n "[Removing installation directory]"
        ${RM} ${packageinstdir}
        nothingtodo="FALSE"
      fi

      # Finish
      if [ -z "${nothingtodo}" ]; then
        echo -n "[Nothing to do]"
      fi
      echo ""
    done
    exit 0;;
  "status"        )
    echo "${oprefix} Installation status of selected packages:"
    for package in ${pkgs}; do
      echo "${oprefix} ${package}:"
      eval packageinstdir="${benchdir}/\${group_${package}}/${package}/inst"
      if [ -d "${packageinstdir}" ]; then
        build_arch_dirs="$(${LS} ${packageinstdir})"
        if [ -z "${build_arch_dirs}" ]; then
          echo -e "${oprefix}   -no installations-"
        else
          found_insts="FALSE"
          for archdir in $build_arch_dirs; do
            if [ -f "${packageinstdir}/${archdir}/build-info" ]; then
              echo -e "${oprefix}   ${archdir}"
              found_insts="TRUE"
            fi
          done
          if [ "${found_insts}" == "FALSE" ]; then
            echo -e "${oprefix}   -no installations-"
          fi
        fi
      else
        echo -e "${oprefix}   -no installations-"
      fi
    done
    exit 0;;
  "info"        )
    echo "${oprefix} Information about this PARSEC distribution:"
    parsecversion="$(${CAT} ${PARSECDIR}/version)"
    echo "${oprefix}   Version: ${parsecversion}"
    echo
    # List all available packages
    echo "${oprefix}   Available packages:"
    for package in ${alias_all}; do
      eval packagegroup="\${group_${package}}"
      echo "${oprefix}     - ${package} (${packagegroup})"
    done
    echo
    # List all available inputs
    echo "${oprefix}   Available input sets:"
    for inputfile in ${PARSECDIR}/config/*.runconf; do
      inputname=$(${BASENAME} ${inputfile})
      inputname=${inputname/%.runconf/}
      source ${inputfile}
      if [ -z "${run_desc}" ]; then
        echo "${oprefix}     ${inputname} - (Description not available)"
      else
        echo "${oprefix}     ${inputname} - ${run_desc}"
      fi
    done
    echo
    # List all supported build configurations
    echo "${oprefix}   Supported build configurations:"
    build_config_files=$(ls ${PARSECDIR}/config/*.bldconf)
    echo -n "${oprefix}     "
    for file in ${build_config_files}; do
      config_file_base=$(${BASENAME} ${file})
      echo -n "${config_file_base%.bldconf} "
    done
    echo
    exit 0;;
  *           )
    echo "${oprefix} Error: Unknown action '${action}'."
    exit 1;;
esac

exit 0
